// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: Implementation of CDateTime.
//   $Id$

#include "stdafx.h"
#include "DateTime.h"
#include <limits>


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CDateTime::CDateTime()
{

}

CDateTime::~CDateTime()
{

}

HRESULT CDateTime::to_ISO8601(SYSTEMTIME t, BSTR *psResult)
{
	char caBuf[5];
	CComBSTR sBuf;
	CComBSTR sTime;
	//year
	_ltoa(t.wYear,caBuf,10);
	sTime = caBuf;
	sTime += "-";
	//month
	numberToZeroFilledString(t.wMonth,2,&sBuf);
	sTime += sBuf;
	sTime += "-";
	sBuf.Empty();
	//day
	numberToZeroFilledString(t.wDay,2,&sBuf);
	sTime += sBuf;
	sBuf.Empty();
	//time
	sTime += "T";
	//hours
	numberToZeroFilledString(t.wHour,2,&sBuf);
	sTime += sBuf;
	sBuf.Empty();
	sTime += ":";
	//minutes
	numberToZeroFilledString(t.wMinute,2,&sBuf);
	sTime += sBuf;
	sBuf.Empty();
	sTime += ":";
	//seconds
	numberToZeroFilledString(t.wSecond,2,&sBuf);
	sTime += sBuf;
	sBuf.Empty();
	
	TIME_ZONE_INFORMATION tz;
	GetTimeZoneInformation(&tz);
	if(tz.Bias >= 0)
		sTime += "+";
	numberToZeroFilledString(static_cast<long>(floor(static_cast<double>(tz.Bias / 60))), 2, &sBuf);
	sTime += sBuf;
	sBuf.Empty();
	sTime += ":";
	numberToZeroFilledString(tz.Bias % 60,2,&sBuf);
	sTime += sBuf;
	sBuf.Empty();

	//Copy the completed date/time string to the return buffer and exit.
	*psResult = sTime.Detach();
	return S_OK;
}

HRESULT CDateTime::from_ISO8601(BSTR sTimeIn,SYSTEMTIME * t)
{
	//TODO: timezone and error checking.

	//"YYYY-MM-DDTHH:mm:ss"

	CString sTime(sTimeIn);
	CString sBuff;
	sBuff = sTime.Left(4);
	sTime.Delete(0,5);
	t->wYear = static_cast<WORD>(strtol(sBuff,NULL,10));
	sBuff = sTime.Left(2);
	sTime.Delete(0,3);
	t->wMonth = static_cast<WORD>(strtol(sBuff,NULL,10));
	sBuff = sTime.Left(2);
	sTime.Delete(0,2);
	t->wDay = static_cast<WORD>(strtol(sBuff,NULL,10));

	//May just be date, no time:
	if(!sTime.IsEmpty())
	{
		sTime.Delete(0);
		sBuff = sTime.Left(2);
		sTime.Delete(0,3);
		t->wHour = static_cast<WORD>(strtol(sBuff,NULL,10));
		sBuff = sTime.Left(2);
		sTime.Delete(0,3);
		t->wMinute = static_cast<WORD>(strtol(sBuff,NULL,10));
		sBuff = sTime.Left(2);
		sTime.Delete(0,3);
		t->wSecond = static_cast<WORD>(strtol(sBuff,NULL,10));
	}
	else
	{
		t->wHour = 0;
		t->wMinute = 0;
		t->wSecond = 0;
	}
	t->wMilliseconds = 0;
	t->wDayOfWeek = 0;
	return S_OK;
}	

HRESULT CDateTime::from_ISO8601E(BSTR bsDuration, SYSTEMTIME *pst, bool * pbNegative)
{
	HRESULT hr = S_OK;
	CString sDuration(bsDuration);
	
	pst->wDay = 0;
	pst->wDayOfWeek = 0;
	pst->wMonth = 1;
	pst->wYear = 0;
	pst->wHour = 0;
	pst->wMinute = 0;
	pst->wSecond = 0;
	pst->wMilliseconds = 0;

	 *pbNegative = sDuration[0] == '-';

	int iStartAt = *pbNegative?2:1;
	int iUnitMarker = sDuration.Find('Y');
	if(iUnitMarker > -1)
	{
		hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wYear);
		iStartAt = iUnitMarker + 1;
	}
	
	if(SUCCEEDED(hr))
	{
		//M after T is minutes, m before t is months.
		int iTimeMarker = sDuration.Find('T',iStartAt);
		iUnitMarker = sDuration.Find('M',iStartAt);
		if(iTimeMarker == -1 || iUnitMarker < iTimeMarker)
		{
			if(iUnitMarker > -1)
			{
				hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wMonth);
				iStartAt = iUnitMarker + 1;		
			}
		}
	}

	if(SUCCEEDED(hr))
	{
		iUnitMarker = sDuration.Find('D',iStartAt);
		if(iUnitMarker > -1)
		{
			hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wDay);
			iStartAt = iUnitMarker + 1;		
		}
	}

	if(SUCCEEDED(hr))
	{
		iUnitMarker = sDuration.Find('T',iStartAt);
		if(iUnitMarker > -1)
		{
			iStartAt = iUnitMarker+1;
				
			if(SUCCEEDED(hr))
			{
				iUnitMarker = sDuration.Find('H',iStartAt);
				if(iUnitMarker > -1)
				{
					hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wHour);
					iStartAt = iUnitMarker + 1;		
				}
			}

			if(SUCCEEDED(hr))
			{
				iUnitMarker = sDuration.Find('M',iStartAt);
				if(iUnitMarker > -1)
				{
					hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wMinute);
					iStartAt = iUnitMarker + 1;		
				}
			}
			if(SUCCEEDED(hr))
			{
				iUnitMarker = sDuration.Find('S',iStartAt);
				if(iUnitMarker > -1)
				{
					int iPointMarker = sDuration.Find('.');
					if(iPointMarker > -1)
					{
	 					hr = getNumber(sDuration,iStartAt,iPointMarker-iStartAt,pst->wSecond);
						iStartAt = iPointMarker + 1;

						if(SUCCEEDED(hr))
						{
							hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wMilliseconds);
							//power up depending on the number of characters after the decimal.
							// 0.3 == 300ms; 0.03 = 30ms; 0.003 = 3ms;
							//0.30 == 300ms; 0.030 = 30ms;
							
							pst->wMilliseconds *= static_cast<WORD>(pow(10,(3-(iUnitMarker-iStartAt))));
						}
					}
					else
					{
						hr = getNumber(sDuration,iStartAt,iUnitMarker-iStartAt,pst->wSecond);
						iStartAt = iUnitMarker + 1;
					}
				}
			}
		}
	}

	return hr;
}

HRESULT CDateTime::getNumber(CString &sSource, int iFirst, int iLen, unsigned short & lTarget)
{
	HRESULT hr;
	CString sNumber = sSource.Mid(iFirst,iLen);
	//don't allow negatives (possibly a parameter, see later.)
	if(sNumber.Find('-') == -1)
	{
		lTarget = static_cast<unsigned short>(strtoul(sNumber,NULL,10));
		hr = S_OK;
	}
	else
	{
		hr = E_FAIL;
	}
	return hr;
}
